grammar ttcn3;

options {
output=AST;
ASTLabelType=CommonTree;
backtrack=true;
///k=3;
}

tokens  {
	FUNC_CALL;
	BLOCK;
	UNARY_EXPR;
	ACTUAL_ARGS;
	FORMAL_ARGS;
	FORMAL_VAR;
	//ASSIGN_VAR;
	FIELD_ACCESS;
	RUNS_ON;
	PORT_REF;
	SINGLE_CONNECT;
}

@header{
package org.yatr.yatrc;

import java.util.HashMap;
import java.util.LinkedHashMap;
import org.yatr.yatrc.TType;
import static org.yatr.yatrc.TType.*;
import static org.yatr.yatrc.Log.*;
import static org.yatr.yatrc.Symtbl.*;
import org.yatr.yatrc.Symtbl.*;
}

@lexer::header{
package org.yatr.yatrc;
}

@lexer::members{
}

@members{

// save the var/const definition info
TType gidType=null;
Token gidToken=null;

HashMap<String, Symtbl> symtblOfModules;

HashMap<String, Symtbl> getSymtblOfModules() {
	return symtblOfModules;
}

/*
 * if errorCount>0, the skip AST walker.
 */
int errorCount = 0;
int  getTtcnErrorCount() {
	int err= errorCount + errorCount();
	return err;
}

}

ttcn3File
@init{
	symtblOfModules=new LinkedHashMap<String, Symtbl>();
}
@after{
	/*
	 * check for UNDEFINED types!!
	 */
}
	:	ttcn3Modules
	//|	EOF
	;
ttcn3Modules
	:	ttcn3Module + //{System.out.println("\n\ttree="+$ttcn3Module.tree.toStringTree()+"\n");}
	|	EOF
	;

ttcn3Module
scope{
	Symtbl symtbl;
	Token moduleIDName;
}
@init{
	$ttcn3Module::symtbl = new Symtbl();
}
@after{
symtblOfModules.put($ttcn3Module::moduleIDName.getText(), $ttcn3Module::symtbl);
}
	:	'module' id 
	    {
	        System.err.println("Parsering Module \""+$id.text+"\"...");
	        $ttcn3Module::moduleIDName=$id.token;
		    $ttcn3Module::symtbl.addType($id.token, $ttcn3Module::symtbl.newModule($id.token));
		}
		'{'
			moduleDefinitionList?
			moduleControlPart?
		'}' 
		
		-> ^('module' id moduleDefinitionList? moduleControlPart?)
	;

RCURLY
	:	'}';
LCURLY
	:	'{';

semiColon
	:   ';'
	|   { (input.LA(-1)==RCURLY) || (input.LA(1)==RCURLY)}? (';')?
	;

moduleControlPart
    : 'control' statementBlock
      -> ^('control' statementBlock)
    ;

moduleDefinitionList
	:	(moduleDefinition  semiColon )+ 	// ';'?
		-> moduleDefinition+
	;

moduleDefinition
	:	typeDef
	|	constDef
	|	functionDef
	|	importDef
	|   testcaseDef
	;

importDef
	:	'import'^ 'from' id 'all'
	;

typeDef
	: 'type'^ typeDefBody
	;
typeDefBody
	: ('record' | 'component') => structuredTypeDef
	|subTypeDef
	;
structuredTypeDef
options{k=1;}
	: recordDef
	| componentDef
	;

recordDef
	:	'record'^ structDefBody
	;

structDefBody
@init{
	TType t=null;
}
	:	id  {
			t = $ttcn3Module::symtbl.findType($id.token, TAttrib.TYPE, null);
			if(null == t) {
				t = $ttcn3Module::symtbl.newType($id.token, TSubAttribTYPE.RECORD);
				$ttcn3Module::symtbl.addType($id.token, t);
			}
			t.doSubType($id.token, TSubAttribTYPE.RECORD);
		}
		'{' structFieldDef[t] (',' structFieldDef[t])* 
		'}'
		->^(id structFieldDef+)
	;
structFieldDef [TType t]
	:	oldtype1=typeName id1=id {t.addSubTableItem($id1.token, newValue($id1.token, TSubAttribVALUE.VAR, $oldtype1.t));} 	//'optional'?
		-> ^(id typeName)
	;

subTypeDef
	:	typeName id
		{
			TType t = $ttcn3Module::symtbl.findType($id.token, TAttrib.TYPE, null);
			if(null == t) {
		 		$ttcn3Module::symtbl.addType($id.token, newType($id.token, (TSubAttribTYPE)$typeName.t.getSubAttrib()));
		 		t = $ttcn3Module::symtbl.findType($id.token, TAttrib.TYPE, null);
		 	}
		 	t.setToken($id.token);
		 	
		 	if($typeName.t.getSubAttrib()!=TSubAttribTYPE.UNKNOWN) {
 				 	t.doSubType($typeName.t);
		 	} else {
		 			$typeName.t.appendSubType(t);
		 	}
		}
		->^(id typeName)
	;
/*
typeDef
	:	'type'^ typeName id
		{
			TType t = $ttcn3Module::symtbl.findType($id.token, TAttrib.TYPE, null);
			if(null == t) {
		 		$ttcn3Module::symtbl.addType($id.token, newType($id.token, (TSubAttribTYPE)$typeName.t.getSubAttrib()));
		 		t = $ttcn3Module::symtbl.findType($id.token, TAttrib.TYPE, null);
		 	}
		 	t.setToken($id.token);
		 	
		 	if($typeName.t.getSubAttrib()!=TSubAttribTYPE.UNKNOWN) {
 				 	t.doSubType($typeName.t);
		 	} else {
		 			$typeName.t.appendSubType(t);
		 	}
		}
	|	'type'^ 'record' id { 
				TType t2 = $ttcn3Module::symtbl.findType($id.token, TAttrib.TYPE, null);
				if(null == t2) {
					t2 = $ttcn3Module::symtbl.newType($id.token, TSubAttribTYPE.RECORD);
					$ttcn3Module::symtbl.addType($id.token, t2);
				}
				t2.doSubType($id.token, TSubAttribTYPE.RECORD);
		}
	   typeDefBody[t2]
	;

typeDefBody [TType t]
	:	'{'
		     (  oldtype1=typeName id1=id  {t.addSubTableItem($id1, newValue($id1, TSubAttribVALUE.VAR, $oldtype1.t));} 
		        (',' oldtype2=typeName id2=id {t.addSubTableItem($id2, newValue($id1, TSubAttribVALUE.VAR, $oldtype2.t));} )* 
		     )?	//'optional'?
		'}'
		-> (typeName id)*
	;
*/

componentDef
@init{
	TType type=null;
}
	:	'component' id
		 {
		   	 //type = addID2Module($ttcn3Module::symtbl, $id.token, TAttrib.COMPONENT);
		   	 type = $ttcn3Module::symtbl.findType($id.token, TAttrib.TYPE, null);
		   	 if(null == type) {
		   	 	 type=$ttcn3Module::symtbl.newType($id.token, TSubAttribTYPE.COMPONENT);
		   	 	 $ttcn3Module::symtbl.addType($id.token, type);
		   	 	 type.doSubType($id.token, TSubAttribTYPE.COMPONENT);
		   	} else {
		   		logError($id.token, "multi-defined symbol.");
		   		if(type.getToken()!=null)
		   			logError(type.getToken(), "previous defined here.");
		   	 }
		   	 
		 }
		'{'
		   componentDefList[type]?
		'}'
		-> ^('component' id componentDefList? )
	;
componentDefList [TType type]
	:	(componentElementDef[$type] semiColon)+ 
		-> componentElementDef+
	;
componentElementDef [TType t]
	:	varInstance  {$t.addSubTableItem(gidToken, newValue(gidToken, TSubAttribVALUE.VAR, gidType)); 
				//System.out.println("varInstance token="+gidToken.getText());
				}
	|	constDef     {$t.addSubTableItem(gidToken, newValue(gidToken, TSubAttribVALUE.VAR, gidType));}
	//| portInstance | timerInstance
	;

typeName returns [TType t]
@init{
TSubAttribTYPE subt = TSubAttribTYPE.UNKNOWN;
}
	:	pt='integer'	{$t = getPredefinedType($pt.text);}
	|	pt='charstring' {$t = getPredefinedType($pt.text);}
	|	pt='boolean'	{$t = getPredefinedType($pt.text);}
	|	pt2=id		{subt = TSubAttribTYPE.UNKNOWN;}
		{	$t = $ttcn3Module::symtbl.findType($pt2.token, TAttrib.TYPE, null);
			 if($t==null) {
			 	TType tt=$ttcn3Module::symtbl.newType($pt2.token, subt);
			 	$ttcn3Module::symtbl.addType($pt2.token, tt);
			 	$t = tt;
			}
		}
	;

constDef
@init{
 TType t=null;
 gidType=null;
 gidToken=null;
}
@after{
// gidType=null;
// gidToken=null;
}
	:	'const'  typeName { t = $typeName.t;}
	            constInitial[t] (',' constInitial[t])*
		-> ^('const' typeName constInitial+ )
		//-> ^('const' type constID constExpr)+
		//-> ^('const' type (constID constExpr)+ )
	;
constInitial [TType t]
	:	id ':='^ constExpr	{
					TType v = $ttcn3Module::symtbl.newValue($id.token, TSubAttribVALUE.CONST, $t);
					$ttcn3Module::symtbl.addType($id.token, v);
					
					///
					gidType=$t;
					gidToken=$id.token;
				}
	;

functionDef
@init{
 TType type=null;
}
	:	'function' id 
	   {
	   	 //type = addID2Module($ttcn3Module::symtbl, $id.token, TAttrib.FUNCTION);
	   	 
	   	 type = $ttcn3Module::symtbl.findType($id.token, TAttrib.FUNCTION, null);
	   	 if(null == type) {
	   	 	 type=$ttcn3Module::symtbl.newFunction($id.token);
	   	 	 $ttcn3Module::symtbl.addType($id.token, type);
	   	} else {
	   		logError($id.token, "multi-defined symbol.");
	   		if(type.getToken()!=null)
	   			logError(type.getToken(), "previous defined here.");
	   	}
	  }
	  '(' functionFormalParList[type]? ')' runsOnSpec? returnType? 
	  {
	   	if($returnType.t!=null){
	   		type.setType($returnType.t);
	   	}

	   	if($runsOnSpec.compName!=null) {
	   		//System.out.println("func:"+$id.text+" runs on "+$runsOnSpec.compName);
	   		((FunctionType)type).setRunsOnComp($runsOnSpec.compName);
	   	}
	   }
		statementBlock
		->^('function' id functionFormalParList? runsOnSpec? returnType? statementBlock )
	;

returnType returns [TType t]
	:	'return'^ typeName { $t = $typeName.t; }
	;

runsOnSpec returns[String compName]
	:	'runs' 'on' componentType
		{
			$compName = $componentType.name;		
		}//componentType 
		-> ^(RUNS_ON componentType)
	;

functionFormalParList [TType t]
	:	functionFormalPar[t] (',' functionFormalPar[t])*
		-> ^(FORMAL_ARGS functionFormalPar+)
	;

functionFormalPar [TType t]
	:	formalValuePar [t]
	//|	formalTimerPar
	//|	formalTemplatePar
	//|	formalPortPar
	;

formalValuePar [TType t]
	:	/* (in|out|inout) */ typeName id	
	        {
	            TType type = $ttcn3Module::symtbl.findType($id.token, TAttrib.VALUE, null);
	            if(type!=null) {
	                logError($id.token, "multi-defined symbol.");
        	   		if(type.getToken()!=null)
        	   			logError(type.getToken(), "previous defined here.");
	            } else {
	                t.addSubTableItem($id.token, $ttcn3Module::symtbl.newValue($id.token, TSubAttribVALUE.VAR, $typeName.t));
	            }
	        }
		-> ^(FORMAL_VAR typeName id)
	;

statementBlock
	:	lc='{' functionStatementOrDefList? '}'
		-> ^(BLOCK[$lc, "Block"] functionStatementOrDefList? )
	;
functionStatementOrDefList
	:	(functionStatementOrDef   semiColon )+
		-> functionStatementOrDef+
	;
functionStatementOrDef
	:	functionLocalDef
	|	functionStatement
	;
functionLocalDef
	:	constDef
	|	varInstance
	;

/**
 * testcase definitions.
 */
testcaseDef
@init{
	TType type=null;
}
	:	'testcase'^ id  
		 {
		   	 //type = addID2Module($ttcn3Module::symtbl, $id.token, TAttrib.TESTCASE);
		   	 
		   	 type = $ttcn3Module::symtbl.findType($id.token, TAttrib.TESTCASE, null);
		   	 if(null == type) {
		   	 	 type=$ttcn3Module::symtbl.newTestcase($id.token);
		   	 	 $ttcn3Module::symtbl.addType($id.token, type);
		   	} else {
		   		logError($id.token, "multi-defined symbol.");
		   		if(type.getToken()!=null)
		   			logError(type.getToken(), "previous defined here.");
		   	}
		 }
		'(' testcaseFormalParList[type]? ')' /*configSpec*/ runsOnSpec systemSpec? statementBlock
		{
	   		((TestcaseType)type).setRunsOnComp($runsOnSpec.compName);
			if($systemSpec.systemCompName!=null) {
		   		//System.out.print("case:"+$id.text+" runs on "+$runsOnSpec.compName+" system on "+$systemSpec.systemCompName);
		   		((TestcaseType)type).setSystemOnComp($systemSpec.systemCompName);
		   	} else {
		   		//System.out.print("case:"+$id.text+" runs on "+$runsOnSpec.compName);
		   	}
		}
	;
/*configSpec
	:	runsOnSpec systemSpec?
	;
*/
systemSpec returns[String systemCompName]
	:	'system'^ id {$systemCompName = $id.text;}
	;
testcaseFormalParList [TType t]
	:	testcaseFormalPar[t] (',' testcaseFormalPar[t])*
		-> ^(FORMAL_ARGS testcaseFormalPar+)
	;
testcaseFormalPar[TType t]
	:	formalValuePar[t]
	//|	formalTemplatePar
	;

varInstance
@init{
 TType t=null;
 gidType=null;
 gidToken=null;
}
@after{
// gidType=null;
// gidToken=null;
}
	:	'var' typeName {t = $typeName.t;}  var[t] (',' var[t])*
		-> ^('var' typeName var+)
		//-> ^('var' type var)+
	;

var  [TType t]
    	:	id	{gidType=$t; gidToken=$id.token;}
	|	id ':='^ expr 	{gidType=$t; gidToken=$id.token;}
	;

functionStatement
	:	basicStatements
	|	behaviourStatements
	|	configurationStatements
	;
basicStatements
	:	assignment
	|	logStatement
	|	loopConstruct
	|	conditionalConstruct
	//|	selectCaseConstruct
	;
behaviourStatements
	:	returnStatement
	|	functionInstance
	|	testcaseInstance
	;

/**
 * Component operations.
 */
configurationStatements
	: startTCStatement
	| connectStatement
	| disconnectStatement
	| mapStatement
	| unmapStatement
	/*| stopTCStatement
	| killTCStatement
	| killedStatement
	| doneStatement*/
	;
/*
stopTCStatement
	: StopKeyword
	| componentReferenceOrLiteral Dot StopKeyword
	| AllKeyword ComponentKeyword Dot StopKeyword
	;

componentReferenceOrLiteral
	: componentOrDefaultReference
	| MTCKeyword
	| SelfKeyword
	;
killTCStatement
	: KillKeyword
	| componentReferenceOrLiteral KillKeyword
	| AllKeyword ComponentKeyword Dot KillKeyword
	;
doneStatement
	: componentID Dot DoneKeyword
	;
killedStatement
	: componentID Dot KilledKeyword
	;
*/
connectStatement
	: 'connect'^ singleConnectionSpec
	;

singleConnectionSpec
	: '(' portRef ',' portRef ')'
		-> ^(SINGLE_CONNECT portRef portRef)
	;
portRef
	: componentRef ':' port
		-> ^(PORT_REF componentRef port)
	;
componentRef
	: componentOrDefaultReference 
	| 'system' 
	| 'self' 
	| 'mtc'
	;
port	//(portIdentifier | portParIdentifier)	//arrayOrBitRef?
	:	portIdentifier
	//|	portParIdentifier
	;
portIdentifier
	:	id
	;
//portParIdentifier
//	:	id
//	;

disconnectStatement
	: 'disconnect'^ (singleOrMultiConnectionSpec)? 
	;
singleOrMultiConnectionSpec
	: singleConnectionSpec
	/*| allConnectionsSpec
	| allPortsSpec
	| allCompsAllPortsSpec*/
	;
/*
allConnectionsSpec
	: '(' portRef ')'
		-> portRef
	;
allPortsSpec
	: '(' componentRef ':' 'all' 'port' ')'
	;
allCompsAllPortsSpec
	: '(' 'all' 'component' ':'  'all' 'port' ')'
	;
*/
mapStatement
	: 'map'^ singleConnectionSpec
	;

unmapStatement
	: 'unmap'^ (singleOrMultiConnectionSpec)?
	;


startTCStatement
	:	componentOrDefaultReference '.' 'start' '(' functionInstance ')'
		-> ^('start' componentOrDefaultReference functionInstance)
	;
componentOrDefaultReference
	:	variableRef
	//|	functionInstance
	;

testcaseInstance
	:	'execute' '(' id '(' testcaseActualParList? ')' /*(',' timerValue)?*/ ')'
		-> ^('execute' id testcaseActualParList?)
	;
testcaseActualParList
	:	actualArgs
	;
/*
testcaseActualPar
	:	templateInstance
	;

templateInstance
	:	inLineTemplate
	;
*/
loopConstruct
	:	'for' '(' initExpr ';' conditionExpr ';' stepExpr ')' statementBlock
		-> ^('for' initExpr conditionExpr stepExpr statementBlock)
	;
initExpr
	:	varInstance
	|	assignment
	;
conditionExpr
	:	boolExpr
	;
stepExpr
	:	assignment
	;

conditionalConstruct
	:	'if' '(' boolExpr ')' s1=statementBlock 
		(
		    'else' s2=statementBlock -> ^('if' boolExpr $s1 $s2)
		  //|  'else' 'if' s3=statementBlock -> 
		  |	/*empty*/	 -> ^('if' boolExpr $s1)
		)
	;

logStatement
	:	'log' '(' actualArgs? ')'
		-> ^('log' actualArgs?)
	;
functionInstance
	:	id '(' actualArgs? ')'
		-> ^(FUNC_CALL id actualArgs?)
	;
actualArgs
	:	expr (',' expr)*
		-> ^(ACTUAL_ARGS expr+)
	;
returnStatement
	:	'return' expr?	-> ^('return' expr?)
	;

assignment
	:	variableRef ':='^ expr
	;
variableRef
	:	id ('.' id)+ -> ^(FIELD_ACCESS id id+)
	|   id
	;

//extendedFieldRef
//	:	('.' id)+
//		-> id+
//	;

constExpr
	:	expr
	;
boolExpr
	:	expr
	;

expr
	:	relExpr ( ('=='^ | '!='^ ) relExpr)*
	;
relExpr
	:	addExpr ( ('<'^ | '<='^ | '>'^ | '>='^) addExpr)?
	;
addExpr
	:	mulExpr ( ('+'^ | '-'^ | '&'^ ) mulExpr)*
	;
mulExpr
	:	unaryExpr ( ('*'^ | '/'^ | 'mod'^) unaryExpr)*
	;
unaryExpr
	:	'-' primary -> ^(UNARY_EXPR '-' primary)
	|	'+' primary -> ^(UNARY_EXPR '+' primary)
	|	primary
	;
primary
options{
k=*;
}
	:	value
	|	'(' expr ')' -> expr
	|	opCall
	;
value //returns[TType t]
	:	INT		//{$t = newType(TAttrib.VALUECONST, TSubAttribTYPE.INTEGER);}
	|	Cstring	//{$t = newType(TAttrib.VALUECONST, TSubAttribTYPE.CHARSTRING);}
	|	('true' | 'false') //{$t = newType(TAttrib.VALUECONST, TSubAttribTYPE.BOOLEAN);}
	|	variableRef	           //{$t = findType($ttcn3Module::symtblsModule, $id.token, null, null);
				 // if($t == null) $t = newType(TAttrib.UNKNOWN, TSubAttribTYPE.UNKNOWN);
				//}
    |   functionInstance
	;

opCall
	:	configurationOps
	//|	timerOps
	//|	verdictOps
	//...
	;
configurationOps
	:	createOp
	/*|	'self'
	|	'system'
	|	'mtc'*/
	//|	runningOp
	//|	aliveOp
	;
createOp
	:	componentType '.' 'create'	// ( '(' singleExpression ')' )? 'alive'?
		->^('create' componentType)
	;
componentType	returns[String name]
	:	id	{$name=$id.text;}
	;


id returns[Token token, String text]
    :   ID
        {
            $token = $ID;
            $token.setText($ID.text+"_");
            $text = $token.getText();
        }
    ;

ID	:	('a'..'z' | 'A'..'Z') ('a'..'z' | 'A'..'Z' | '0'..'9'| '_')*;
INT	:	('0' | ('1'..'9') ('0'..'9')*);
Cstring
	: '"' ( EscapeSequence | ~('\\'|'"') )* '"'
	;

fragment
EscapeSequence
	:	'\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
	;

WS	: ('\t' | ' ' | '\r' | '\n' )+ {$channel=HIDDEN;}
	;

COMMENT
    :   '/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;}
    ;

LINE_COMMENT
    : '//' ~('\n'|'\r')* '\r'? '\n' {$channel=HIDDEN;}
    ;
